package abc.base;

import abc.converter.BaseExcelConverter;
import abc.converter.IExcelConverters;
import abc.exception.ParamException;
import abc.handler.*;
import abc.database.DataExecuor;
import abc.annotation.*;
import abc.enums.ActionType;
import abc.filter.RequestMatchSchemaFilter;
import abc.listener.ExcelListener;
import abc.model.*;
import abc.utils.*;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.annotations.*;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.codec.multipart.FilePart;
import org.springframework.http.codec.multipart.FormFieldPart;
import org.springframework.http.codec.multipart.Part;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import org.springframework.core.io.buffer.DataBuffer;

import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.net.URLEncoder;
import java.security.NoSuchAlgorithmException;
import java.sql.SQLException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

@Api
@Slf4j
@Permission
@ServiceAccess
@RestController
public abstract class AbcBaseController<T extends Serializable, R extends Serializable> implements IBaseApi<T,R>,  IBaseResponse {
    @Autowired
    protected ApplicationContext applicationContext;

    @Autowired
    private ResourceLoader resourceLoader;

    @Autowired
    protected DataExecuor dataExecuor;

    @Autowired
    protected RequestMatchSchemaFilter requestMatchSchemaFilter;

    @Autowired
    protected BllFactory bllFactory;

    @Autowired(required = false)
    IUploadHandler uploadHandler;

    @Autowired(required = false)
    IDataImportHandler dataImportHandler;

    @Autowired(required = false)
    IDataExportHandler dataExportHandler;

    @Autowired(required = false)
    private IPlayVideoHandler playVideoHandler;

    @Autowired(required = false)
    private IExcelConverters excelConverters;

    private static final String LOG_PRE_SUFFIX = "ABC-CONTROLLER-LOGGER";
    @Value("${abc.md5.publicKey:}")
    private String md5PublicKey;

    @Value("${abc.upload.path:}")
    private String baseUploadPath;

    @Value("${abc.upload.maxSize:0}")
    private Long uploadMaxSize;

    @Value("${abc.upload.fileType:}")
    private String uploadFileType;

    private static List<String> supportFileTypeList = new ArrayList<>();

    private IPlayVideoHandler getPlayVideoHandler() {
        if(playVideoHandler == null || Pattern.matches("jdk\\.proxy\\d+\\.\\$Proxy\\d+", playVideoHandler.getClass().getName())) {
            playVideoHandler = new IPlayVideoHandler() {
                @Override
                public Mono<Void> call(Resource resource, ServerWebExchange exchange) {
                    try{
                        DataBuffer dataBuffer = exchange.getResponse().bufferFactory().allocateBuffer();
                        // 获得 OutputStream 的引用
                        OutputStream outputStream = dataBuffer.asOutputStream();
                        InputStream in = resource.getInputStream();
                        byte[] buffer = new byte[1024];
                        int len = in.read(buffer);
                        while (len != -1) {
                            outputStream.write(buffer, 0, len);
                            len = in.read(buffer);
                        }
                        return exchange.getResponse().writeWith(Mono.just(dataBuffer));
                    }
                    catch (Exception e) {
                        log.error(e.getMessage(), e);
                    }
                    return exchange.getResponse().writeWith(Mono.empty());
                }
            };
        }
        return playVideoHandler;
    }

    private IUploadHandler getUploadHandler() {
        if(uploadHandler == null || Pattern.matches("jdk\\.proxy\\d+\\.\\$Proxy\\d+", uploadHandler.getClass().getName())) {
            uploadHandler = new IUploadHandler() {
                @Override
                public String upload(String basePath, String filename, List<byte[]> data) {
                    try {
                        return UploadUtil.upload(basePath, filename, data);
                    } catch (IOException e) {
                        log.error(e.getMessage(), e);
                    }
                    return null;
                }

                @Override
                public byte[] download(String uploadPath) {
                    File file = new File(uploadPath);
                    if("/".equals(uploadPath.substring(0,1)) && !file.exists()) {
                        uploadPath = String.format("%s%s", System.getProperty("user.dir"), uploadPath);
                        file = new File(uploadPath);
                    }

                    byte[] bytes = UploadUtil.getFileByteArray(file);

                    return bytes;
                }
            };
        }
        return uploadHandler;
    }

    private IDataExportHandler getDataExportHandler() {
        if(dataExportHandler == null || Pattern.matches("jdk\\.proxy\\d+\\.\\$Proxy\\d+", dataExportHandler.getClass().getName())) {
            dataExportHandler = new IDataExportHandler() {
                @Override
                public <T extends BaseModel> byte[] call(List<T> modelList) {
                    var result = new ArrayList<>();
                    modelList.forEach(a-> {
                        try {
                            result.add(dataExecuor.toVo(a, ActionType.EXPORT));
                        } catch (Exception e) {
                            log.error(LOG_PRE_SUFFIX + "ERROR=====> ", e);
                        }
                    });

                    ByteArrayOutputStream bos = new ByteArrayOutputStream();
                    var writerBuilder = EasyExcel.write(bos);
                    BaseExcelConverter.converts.forEach(converter -> {
                        writerBuilder.registerConverter(converter);
                    });
                    if(excelConverters != null && excelConverters.getConvertList() != null) {
                        excelConverters.getConvertList().forEach(converter -> {
                            writerBuilder.registerConverter(converter);
                        });
                    }
                    writerBuilder.registerWriteHandler(new ExcelCellWidthStyleHandler());

                    writerBuilder.sheet().head(result.get(0).getClass()).doWrite(result);
                    byte[] bytes = bos.toByteArray();

                    return bytes;
                }
            };
        }
        return dataExportHandler;
    }

    private IDataImportHandler getDataImportHandler() {
        if(dataImportHandler == null || Pattern.matches("jdk\\.proxy\\d+\\.\\$Proxy\\d+", dataImportHandler.getClass().getName())) {
            dataImportHandler = new IDataImportHandler() {
                @Override
                public <T extends BaseModel> void call(String dbname, String table, T model, DataBuffer dataBuffer) {

                    List<String> fieldList = new ArrayList<>();
                    byte[] buffer = dataBuffer.toByteBuffer().array();
                    List<SheetHeaderModel> headerList = null;
                    try {
                        List<String> pkList = dataExecuor.elExpressionQueryHelper.getPrimaryKeyNames(requestMatchSchemaFilter.getDBName(), table);
                        headerList = OfficeUtil.getHeadersByExcel(buffer, fieldList);
                        EasyExcel.read(new ByteArrayInputStream(buffer), dataExecuor.toVoBean(model, ActionType.IMPORT).getClass(), new ExcelListener(table, model, headerList, pkList)).sheet().doRead();
                    } catch (Exception e) {
                        log.error(e.getMessage(), e);
                    }
                }
            };
        }
        return dataImportHandler;
    }

    /***
     * 打印系统时间
     * @return
     */
    @Pass
    @ResponseBody
    @Operation(summary = "打印系统时间", method = "GET")
    @RequestMapping( value = "time", method = RequestMethod.GET)
    public ResponseData<String> timestamp() {
        return success(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date()));
    }

    /***
     * 根据ID获取数据
     * @param id
     * @return
     */
    @PostAction
    @GetByIdAction
    @ResponseBody
    @Operation(summary = "根据ID获取数据", method = "GET")
    @RequestMapping( value = "{id}", method = RequestMethod.GET  )
    public ResponseData<R> doGet(@Parameter(description = "ID", required = true) @PathVariable Long id)  {
        try {
            String table = requestMatchSchemaFilter.getTableName(this.getClass());
            var model = dataExecuor.getModel(table);
            IBll ibll = bllFactory.getBll(this);
            String dbname = requestMatchSchemaFilter.getDBName();
            Optional<ResponseData> optional = ibll.get(dataExecuor.select(dbname, table, model, id), ActionType.GET);
            if(optional.isPresent()) {
                if(optional.get().getData() instanceof BaseModel<?>) {
                    optional.get().setData(dataExecuor.toVo(optional.get().getData(), ActionType.GET));
                }
                return optional.get();
            }
            return error(100, "Fail to get data!!!");
        }

        catch (Exception e) {
            log.error(LOG_PRE_SUFFIX + "ERROR=====> ", e);

            return error(100, e);
        }
    }

    /***
     * 查询分页数据
     * @param queryData 查询参数
     * @return
     */
    @PostAction
    @QueryPageAction
    @ResponseBody
    @Operation(summary = "查询分页数据", method = "POST")
    @RequestMapping( value = "query/{pageIndex}/{pageSize}", method = RequestMethod.POST  )
    public ResponseData<List<R>> doQueryPage(@Parameter(name="queryData", description = "查询参数", required = true) @RequestBody T queryData,
                                                @Parameter(name="pageIndex", description = "页码", required = true) @PathVariable Long pageIndex,
                                                @Parameter(name="pageSize", description = "每页数据量", required = true) @PathVariable Long pageSize) {

        try {
            List<OrderItem> orders = null;
            if(queryData instanceof Map) {
                if(((Map<?, ?>) queryData).containsKey(dataExecuor.elExpressionQueryHelper.$ORDERS)) {
                    orders = JacksonUtil.toList(((Map<?, ?>) queryData).get(dataExecuor.elExpressionQueryHelper.$ORDERS).toString(), OrderItem.class);
                    ((Map<?, ?>) queryData).remove(dataExecuor.elExpressionQueryHelper.$ORDERS);
                }
            }
            String table = requestMatchSchemaFilter.getTableName(this.getClass());
            var model = dataExecuor.getModel(this.getClass(), queryData);
            IBll ibll = bllFactory.getBll(this);
            IPage page = new Page();
            page.setPages(pageIndex);
            page.setSize(pageSize);
            String dbname = requestMatchSchemaFilter.getDBName();
            Optional<ResponseData> optional = ibll.list(dataExecuor.page(dbname, table, model, page, orders), ActionType.PAGE);
            if(optional.isPresent()) {
                optional.get().setData(dataExecuor.toVo(optional.get().getData(), ActionType.PAGE));
                ResponseData result = optional.get();
                result.setPageInfo(new PageInfo() {{
                    setPageIndex(page.getCurrent());
                    setPageSize(page.getSize());
                    setTotalRecords(page.getTotal());
                }});
                return result;
            }

            return error(110, "Fail to get data list!!!");
        } catch (Exception e) {
            log.error(LOG_PRE_SUFFIX + "ERROR=====> ", e);

            return error(110, e);
        }
    }

    /***
     * 查询数据
     * @param queryData 查询参数
     * @return
     */
    @PostAction
    @QueryAction
    @ResponseBody
    @Operation(summary = "查询数据", method = "POST")
    @RequestMapping( value = "query", method = RequestMethod.POST  )
    public ResponseData<List<R>> doQuery(@Parameter(name="queryData", description = "查询参数", required = true) @RequestBody T queryData) {

        try {
            List<OrderItem> orders = null;
            if(queryData instanceof Map) {
                if(((Map<?, ?>) queryData).containsKey(dataExecuor.elExpressionQueryHelper.$ORDERS)) {
                    orders = JacksonUtil.toList(((Map<?, ?>) queryData).get(dataExecuor.elExpressionQueryHelper.$ORDERS).toString(), OrderItem.class);
                    ((Map<?, ?>) queryData).remove(dataExecuor.elExpressionQueryHelper.$ORDERS);
                }
            }
            String table = requestMatchSchemaFilter.getTableName(this.getClass());
            var model = dataExecuor.getModel(this.getClass(), queryData);

            IBll ibll = bllFactory.getBll(this);

            String dbname = requestMatchSchemaFilter.getDBName();

            Optional<ResponseData> optional = ibll.list(dataExecuor.select(dbname, table, model, orders), ActionType.QUERY);
            if(optional.isPresent()) {
                optional.get().setData(dataExecuor.toVo(optional.get().getData(), ActionType.QUERY));
                return optional.get();
            }

            return error(111, "Fail to get data list!!!");
        } catch (Exception e) {
            log.error(LOG_PRE_SUFFIX + "ERROR=====> ", e);

            return error(111, e);
        }
    }


    /***
     * 查询是否存在数据
     * @param queryData 查询参数
     * @return
     */
    @PostAction
    @ExistAction
    @ResponseBody
    @Operation(summary = "查询是否存在数据", method = "POST")
    @RequestMapping( value = "exist", method = RequestMethod.POST  )
    public ResponseData<Boolean> doExist(@Parameter(name="data", description = "查询参数", required = true) @RequestBody T queryData) {
        try {
            String table = requestMatchSchemaFilter.getTableName(this.getClass());
            var model = dataExecuor.getModel(this.getClass(), queryData);
            IBll ibll = bllFactory.getBll(this);
            String dbname = requestMatchSchemaFilter.getDBName();
            Optional<ResponseData> optional = ibll.get(dataExecuor.first(dbname, table, model, null), ActionType.EXIST);
            if(optional.isPresent()) {
                return success(true);
            }

            return error(112, "Fail to get data list!!!");

        } catch (Exception e) {
            log.error(LOG_PRE_SUFFIX + "ERROR=====> ", e);

            return error(112, e);
        }
    }

    /***
     * 查询统计数据
     * @param queryData 查询参数
     * @return
     */
    @PreAction
    @CountAction
    @ResponseBody
    @Operation(summary = "查询统计数据", method = "POST")
    @RequestMapping( value = "count", method = RequestMethod.POST  )
    public ResponseData<Long> doCount(@Parameter(name="queryData", description = "查询参数", required = true) @RequestBody T queryData) {
        try {
            String table = requestMatchSchemaFilter.getTableName(this.getClass());
            var model = dataExecuor.getModel(this.getClass(), queryData);
            IBll ibll = bllFactory.getBll(this);
            Optional optional = ibll.get(model, ActionType.COUNT);
            if(optional.isPresent()) {
                return success((Long) optional.get());
            }
            else {
                String dbname = requestMatchSchemaFilter.getDBName();
                return success(dataExecuor.count(dbname, table, model));
            }

        } catch (Exception e) {
            log.error(LOG_PRE_SUFFIX + "ERROR=====> ", e);

            return error(113, e);
        }
    }

    /***
     * 新增数据 (可批量)
     * @param postData 提交参数（数组）
     * @return
     */
    @PreAction
    @InsertAction
    @ResponseBody
    @Operation(summary = "新增数据 (可批量)", method = "PUT")
    @RequestMapping( value = "insert", method = RequestMethod.PUT  )
    public ResponseData doInsert(@Parameter(name="postData", description = "插入数据数组", required = true) @RequestBody List<T> postData) throws IOException, SQLException {
        String table = requestMatchSchemaFilter.getTableName(this.getClass());
        var model = dataExecuor.getModel(table);
        List<BaseModel> list = new ArrayList<>();
        for(int i=0; i < postData.size(); i++) {
            T data = postData.get(i);
            model = JacksonUtil.convert(data, model.getClass());
            if(model == null) {
                throw new ParamException("提交参数格式化失败，请检查字段类型是否正确，如字符串时间类型等...");
            }
            list.add(model);
        }
        IBll ibll = bllFactory.getBll(this);
        Optional<ResponseData> optional = ibll.execBatch(list, ActionType.INSERT);
        if(optional.isPresent()) {
            return optional.get();
        }
        else {
            String dbname = requestMatchSchemaFilter.getDBName();
            Map result = dataExecuor.insert(dbname, table, list);

            return success(result);
        }
    }

    /***
     * 插入表单数据（支持上传文件）
     * @return
     */
    @PreAction
    @UploadAction
    @ResponseBody
    @Operation(summary = "插入表单数据（支持上传文件）", method = "PUT")
    @RequestMapping( value = "form/insert", method = RequestMethod.PUT )
    public Mono<ResponseData> doPostFormDataAndInsert(ServerWebExchange exchange) throws Exception {
        if(StringUtil.isEmpty(baseUploadPath)) {
            throw new Exception("Can not find abc.upload.path has been setting!!!");
        }

        AtomicReference<Boolean> isOverMaxSize = new AtomicReference<>(false);
        return exchange.getMultipartData().flatMap(data ->{
            String table = requestMatchSchemaFilter.getTableName(this.getClass());
            var model = JacksonUtil.parse("{}", dataExecuor.getModel(table).getClass());
            List<Field> fields = List.of(model.getClass().getDeclaredFields()).stream().filter(a-> !Modifier.isFinal(a.getModifiers()) &&
                    a.getAnnotation(TableField.class)!=null && a.getAnnotation(TableField.class).exist()).toList();

            data.toSingleValueMap().entrySet().stream()
                    .filter(a-> a.getValue() instanceof FormFieldPart).forEach(b-> {
                        var opt = fields.stream().filter(a-> a.getName().equals(b.getKey())).findAny();
                        if(!opt.isPresent()) {
                            return;
                        }
                        Field fld = opt.get();
                        String value = ((FormFieldPart)b.getValue() ).value();
                        ReflectionUtil.setFieldValue(model, fld.getName(), StringUtil.cast(fld.getType(), value));
                    });

            List<Mono<String>> monoList = new ArrayList<>();
            for(Map.Entry<String, List<Part>> item : data.entrySet()) {
                List<FilePart> partList = item.getValue().stream().filter(a-> a instanceof FilePart).map(b-> (FilePart)b).collect(Collectors.toList());
                String noSupportExtName = getNoSupportUploadFileType(partList);
                if(StringUtil.isNotEmpty(noSupportExtName)) {
                    return Mono.just(error(String.format("No support upload file type by %s !!!", noSupportExtName)));
                }
                partList.forEach(part -> {
                    monoList.add(part.content().map(dataBuffer -> dataBuffer.toByteBuffer().array()).collectList().map(byteList-> {
                        Long len = byteList.stream().mapToLong(a-> a.length).sum();
                        if(uploadMaxSize > 0 && len > uploadMaxSize) {
                            isOverMaxSize.set(true);
                        }
                        if(isOverMaxSize.get()) {
                            return "";
                        }
//                        ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024);
//                        for(byte[] bytePart : byteList) {
//                            byteBuffer.put(bytePart);
//                        }
                        String path = getUploadHandler().upload(baseUploadPath, part.filename(), byteList);
//                        byteBuffer.clear();
                        List<String> pathValueList = new ArrayList<>();
                        pathValueList.add(path);
                        Object fieldValue = ReflectionUtil.getFieldValue(model, item.getKey());
                        if(fieldValue == null) {
                            ReflectionUtil.setFieldValue(model, item.getKey(), pathValueList.get(0));
                        }
                        else {
                            pathValueList.add(fieldValue.toString());
                            ReflectionUtil.setFieldValue(model, item.getKey(), String.join(";", pathValueList));
                        }
                        return path;
                    }));
                });
            }
            if(isOverMaxSize.get()) {
                return Mono.just(error("Over max file size limit!!!"));
            }
            var flux = Flux.fromIterable(monoList).flatMap(Function.identity());
            return flux.collectList().map(paths-> {
                model.insert();
                return success(model.toVo());
            });
        });
    }

    private String getNoSupportUploadFileType(List<FilePart> fileParts) {
        if(supportFileTypeList == null) {
            supportFileTypeList = new ArrayList<>();
        }
        if(supportFileTypeList.size()<1 && StringUtil.isNotEmpty(uploadFileType)) {
            supportFileTypeList = StringUtil.splitString2List(uploadFileType, "[, ;]").stream().map(a-> a.toLowerCase()).collect(Collectors.toList());
        }
        if(supportFileTypeList.size() < 1) {
            return null;
        }
        for(FilePart part : fileParts) {
            String extName = StringUtil.splitString2List(part.filename(), "\\.").get(1);
            if(supportFileTypeList.size()>0 && !supportFileTypeList
                    .contains(extName.toLowerCase())) {
                return extName;
            }
        }
        return null;
    }

    /***
     * 修改数据
     * @param id 计算字段
     * @param postData 提交参数
     * @return
     */
    @PreAction
    @UpdateAction
    @ResponseBody
    @Operation(summary = "修改数据", method = "PUT")
    @RequestMapping( value = "{id}", method = RequestMethod.PUT  )
    public ResponseData<Boolean> doUpdate(@Parameter(description = "ID", required = true) @PathVariable Long id,
                                 @Parameter(name="postData", description = "提交参数", required = true) @RequestBody T postData
    ) throws IOException, SQLException {
        String table = requestMatchSchemaFilter.getTableName(this.getClass());
        var model = dataExecuor.getModel(table);
        String dbname = requestMatchSchemaFilter.getDBName();
        model = JacksonUtil.convert(postData, model.getClass());
        if(model==null) {
            throw new ParamException("提交参数格式化失败，请检查字段类型是否正确，如字符串时间类型等...");
        }
        model = dataExecuor.getModelById(dbname, table, model, id);
        IBll ibll = bllFactory.getBll(this);
        Optional<ResponseData> optional = ibll.exec(model, ActionType.UPDATE);
        if(optional.isPresent()) {
            return optional.get();
        }

        Boolean result = dataExecuor.update(dbname, table, model, id);

        return success(result);
    }

    /***
     * 删除数据
     * @param id
     * @return
     */
    @PreAction
    @DeleteAction
    @ResponseBody
    @Operation(summary = "删除数据", method = "DELETE")
    @RequestMapping( value = "{id}", method = RequestMethod.DELETE  )
    public ResponseData<Boolean> doDelete(@Parameter(description = "ID", required = true) @PathVariable Long id) throws SQLException {
        String table = requestMatchSchemaFilter.getTableName(this.getClass());
        var model = dataExecuor.getModel(table);
        String dbname = requestMatchSchemaFilter.getDBName();
        model = dataExecuor.getModelById(dbname, table, model, id);
        IBll ibll = bllFactory.getBll(this);
        Optional<ResponseData> optional = ibll.exec(model, ActionType.DELETE);
        if(optional.isPresent()) {
            return optional.get();
        }

        Boolean result = dataExecuor.delete(dbname, table, model, id);

        return success(result);
    }

    /***
     * 批量删除数据
     * @param idList id列表
     * @return
     */
    @PreAction
    @DeleteBatchAction
    @ResponseBody
    @Operation(summary = "批量删除数据", method = "DELETE")
    @RequestMapping( value = "delete", method = RequestMethod.DELETE  )
    public ResponseData<Boolean> deleteByIds(@Parameter(name = "idList", description = "id数组", required = true) @RequestBody List idList) throws SQLException {
        String table = requestMatchSchemaFilter.getTableName(this.getClass());
        var model = dataExecuor.getModel(table);

        Boolean result = false;
        IBll ibll = bllFactory.getBll(this);
        Optional<ResponseData> optional = ibll.exec(model, ActionType.BATCH_DELETE);
        if(optional.isPresent()) {
            return optional.get();
        }

        String dbname = requestMatchSchemaFilter.getDBName();
        result = dataExecuor.delete(dbname, table, model, idList);

        return success(result);
    }

    /***
     * 统计字段最小值
     * @param field 计算字段
     * @param queryData 查询参数
     * @return
     */
    @PreAction
    @MinAction
    @ResponseBody
    @Operation(summary = "统计字段最小值", method = "POST")
    @RequestMapping( value = "min/{field}", method = RequestMethod.POST )
    public ResponseData<String> doGetMin(@Parameter(name="field", description = "字段名", required = true) @PathVariable String field,
                                 @Parameter(name="data", description = "查询参数", required = true) @RequestBody T queryData
    ) {
        try {
            String table = requestMatchSchemaFilter.getTableName(this.getClass());
            var model = dataExecuor.getModel(this.getClass(), queryData);
            IBll ibll = bllFactory.getBll(this);
            Optional<ResponseData> optional = ibll.exec(model, ActionType.MIN);
            if(optional.isPresent()) {
                return optional.get();
            }
            String dbname = requestMatchSchemaFilter.getDBName();
            var value = dataExecuor.min(dbname, table, model, field);
            return success( value );
        } catch (Exception e) {
            log.error(LOG_PRE_SUFFIX + "ERROR=====> ", e);

            return error(150, e);
        }
    }

    /***
     * 统计字段最大值
     * @param field 计算字段
     * @param queryData 查询参数
     * @return
     */
    @PreAction
    @MaxAction
    @ResponseBody
    @Operation(summary = "统计字段最大值", method = "POST")
    @RequestMapping( value = "max/{field}", method = RequestMethod.POST )
    public ResponseData<String> doGetMax(@Parameter(name="field", description = "字段名", required = true) @PathVariable String field,
                                 @Parameter(name="data", description = "查询参数", required = true) @RequestBody T queryData
    ) {
        try {
            String table = requestMatchSchemaFilter.getTableName(this.getClass());
            var model = dataExecuor.getModel(this.getClass(), queryData);
            IBll ibll = bllFactory.getBll(this);
            Optional<ResponseData> optional = ibll.exec(model, ActionType.MAX);
            if(optional.isPresent()) {
                return optional.get();
            }
            String dbname = requestMatchSchemaFilter.getDBName();
            var value = dataExecuor.max(dbname, table, model, field);
            return success( value );
        } catch (Exception e) {
            log.error(LOG_PRE_SUFFIX + "ERROR=====> ", e);

            return error(151, e);
        }
    }

    /***
     * 统计字段之和
     * @param field 计算字段
     * @param queryData 查询参数
     * @return
     */
    @PreAction
    @SumAction
    @ResponseBody
    @Operation(summary = "统计字段之和", method = "POST")
    @RequestMapping( value = "sum/{field}", method = RequestMethod.POST )
    public ResponseData<String> doGetSum(@Parameter(name="field", description = "字段名", required = true) @PathVariable String field,
                                 @Parameter(name="data", description = "查询参数", required = true) @RequestBody T queryData
    ) {
        try {
            String table = requestMatchSchemaFilter.getTableName(this.getClass());
            var model = dataExecuor.getModel(this.getClass(), queryData);
            IBll ibll = bllFactory.getBll(this);
            Optional<ResponseData> optional = ibll.exec(model, ActionType.SUM);
            if(optional.isPresent()) {
                return optional.get();
            }
            String dbname = requestMatchSchemaFilter.getDBName();
            var one = dataExecuor.first(dbname, table, model, null, String.format("sum(`%s`) as %s", field, field));
            var value = ReflectionUtil.getFieldValue(one, StringUtil.getHumpName(field, true));
            return success( value==null? "": value.toString() );
        } catch (Exception e) {
            log.error(LOG_PRE_SUFFIX + "ERROR=====> ", e);

            return error(152, e);
        }
    }

    /***
     * 统计字段平均值
     * @param field 计算字段
     * @param queryData 查询参数
     * @return
     */
    @PreAction
    @AvgAction
    @ResponseBody
    @Operation(summary = "统计字段平均值", method = "POST")
    @RequestMapping( value = "avg/{field}", method = RequestMethod.POST )
    public ResponseData<String> doGetAvg(@Parameter(name="field", description = "字段名", required = true) @PathVariable String field,
                                 @Parameter(name="queryData", description = "查询参数", required = true) @RequestBody T queryData
    ) {
        try {
            String table = requestMatchSchemaFilter.getTableName(this.getClass());
            var model = dataExecuor.getModel(this.getClass(), queryData);
            IBll ibll = bllFactory.getBll(this);
            Optional<ResponseData> optional = ibll.exec(model, ActionType.AVG);
            if(optional.isPresent()) {
                return optional.get();
            }
            String dbname = requestMatchSchemaFilter.getDBName();
            var value = dataExecuor.avg(dbname, table, model, field);
            return success( value );
        } catch (Exception e) {
            log.error(LOG_PRE_SUFFIX + "ERROR=====> ", e);

            return error(153, e);
        }
    }

    /***
     * 上传文件到指定字段
     * @return
     */
    @PreAction
    @UploadAction
    @ResponseBody
    @Operation(summary = "上传文件", method = "POST")
    @RequestMapping( value = "upload", method = RequestMethod.POST )
    public Mono<ResponseData> doJustUpload(@Parameter(name = "file", description = "上传文件", required = true) @RequestPart("file") Flux<FilePart> file
    ) throws Exception {
        if(StringUtil.isEmpty(baseUploadPath)) {
            throw new Exception("Can not find abc.upload.path has been setting!!!");
        }
        AtomicReference<Boolean> hasError = new AtomicReference<>(false);
        AtomicReference<Boolean> isOverMaxSize = new AtomicReference<>(false);
        return file.flatMap(filePart-> {
            String noSupportExtName = getNoSupportUploadFileType(new ArrayList<>() {{ add(filePart); }});
            if(StringUtil.isNotEmpty(noSupportExtName)) {
                hasError.set(true);
                return Mono.just(String.format("No support upload file type by %s!!!", noSupportExtName));
            }
            return filePart.content().map(dataBuffer-> dataBuffer.toByteBuffer().array()).collectList().map(byteList -> {
                Long len = byteList.stream().mapToLong(a-> a.length).sum();
                if(len<0L || (uploadMaxSize > 0L && len > uploadMaxSize)) {
                    isOverMaxSize.set(true);
                }
                if(isOverMaxSize.get()) {
                    return "";
                }
//                ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024);
//                for(byte[] bytePart : byteList) {
//                    byteBuffer.put(bytePart);
//                }
                var path = getUploadHandler().upload(baseUploadPath, filePart.filename(), byteList);
//                byteBuffer.clear();
                return path;
            });
        }).collectList().map(paths-> {
            if(isOverMaxSize.get()) {
                throw new ParamException("Over max file size limit!!!");
            }
            return hasError.get()?error(StringUtil.join("", paths)) :success(paths);
        });
    }


    /***
     * 上传文件到指定字段
     * @return
     */
    @PreAction
    @UploadAction
    @ResponseBody
    @Operation(summary = "上传文件", method = "POST")
    @RequestMapping( value = "{field}/upload/{id}", method = RequestMethod.POST )
    public Mono<ResponseData> doUpload(@Parameter(name="field", description = "字段名", required = true) @PathVariable String field,
                                       @Parameter(description = "ID", required = true) @PathVariable Long id,
                                       @Parameter(name = "file", description = "上传文件", required = true) @RequestPart("file") Flux<FilePart> file
    ) throws Exception {
        if(StringUtil.isEmpty(baseUploadPath)) {
            throw new Exception("Can not find abc.upload.path has been setting!!!");
        }
        String table = requestMatchSchemaFilter.getTableName(this.getClass());
        var model = dataExecuor.getModel(table);
        String dbname = requestMatchSchemaFilter.getDBName();
        model = dataExecuor.getModelById(dbname, table, model, id);
        BaseModel finalModel = model;
        AtomicReference<Boolean> hasError = new AtomicReference<>(false);
        AtomicReference<Boolean> isOverMaxSize = new AtomicReference<>(false);
        return file.flatMap(filePart-> {
            String noSupportExtName = getNoSupportUploadFileType(new ArrayList<>() {{ add(filePart); }});
            if(StringUtil.isNotEmpty(noSupportExtName)) {
                hasError.set(true);
                return Mono.just(String.format("No support upload file type by %s !!!", noSupportExtName));
            }
            return filePart.content().map(dataBuffer-> dataBuffer.toByteBuffer().array()).collectList().map(byteList -> {
                Long len = byteList.stream().mapToLong(a-> a.length).sum();
                if(uploadMaxSize > 0 && len > uploadMaxSize) {
                    isOverMaxSize.set(true);
                }
                if(isOverMaxSize.get()) {
                    return "";
                }
//                ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024);
//                for(byte[] bytePart : byteList) {
//                    byteBuffer.put(bytePart);
//                }
                var path = getUploadHandler().upload(baseUploadPath, filePart.filename(), byteList);
//                byteBuffer.clear();
                return path;
            });
        }).collectList().map(paths-> {
            boolean result = false;
            try{
                if(isOverMaxSize.get()) {
                    throw new ParamException("Over max file size limit!!!");
                }
                if(hasError.get()) {
                    throw new ParamException(StringUtil.join("", paths));
                }
                ReflectionUtil.setFieldValue(finalModel,
                        StringUtil.getHumpName(field, true),
                        StringUtil.join(";", paths)
                );
                IBll ibll = bllFactory.getBll(this);
                Optional<ResponseData> optional = ibll.exec(finalModel, ActionType.UPLOAD);
                if(optional.isPresent()) {
                    return optional.get();
                }
                result = dataExecuor.update(dbname, table, finalModel, id);
            }
            catch (Exception e) {
                log.error(e.getMessage(), e);
                throw new ParamException(e.getMessage());
            }
            if(result) {
                return success(paths);
            }
            else {
                throw new ParamException("fail to update upload field!!!");
            }
        });
    }

    /***
     * 下载指定字段路径文件
     * @param field 路径存放字段
     * @param id 数据id
     * @param index 文件列表索引
     * @return
     */
    @PreAction
    @DownloadAction
    @ResponseBody
    @Operation(summary = "下载文件", method = "GET")
    @RequestMapping( value = "download/{field}/{id}/{index}", method = RequestMethod.GET  )
    public ResponseEntity<byte[]> doDownload(@Parameter(name="field", description = "字段名", required = true) @PathVariable String field,
                                             @Parameter(description = "ID", required = true) @PathVariable Long id,
                                             @Parameter(name = "index", description = "文件索引", required = true) @PathVariable Integer index
    ) throws Exception {
        String table = requestMatchSchemaFilter.getTableName(this.getClass());
        var model = dataExecuor.getModel(table);
        String dbname = requestMatchSchemaFilter.getDBName();
        model = dataExecuor.getModelById(dbname, table, model, id);
        var data = dataExecuor.select(dbname, table, model, id);
        IBll ibll = bllFactory.getBll(this);
        Optional<ResponseData> optional = ibll.exec(model, ActionType.DOWNLOAD);
        if(optional.isPresent()) {
            return null;
        }
        var value = ReflectionUtil.getFieldValue(data, field);
        if(value == null || StringUtil.isEmpty(value.toString())) {
            return null;
        }
        var fileList = StringUtil.splitString2List(value.toString(), ";");

        index--;
        if(fileList.size()<0 || index>=fileList.size()) {
            return null;
        }

        var uploadPath = getUploadPath(fileList.get(index));

        return doDownload(uploadPath);
    }

    @NotNull
    protected ResponseEntity<byte[]> doDownload(String uploadPath) throws UnsupportedEncodingException {
        byte[] bytes = getUploadHandler().download(uploadPath);

        String[] tmp = uploadPath.split("(\\\\|/)");
        String[] fileArr = tmp[tmp.length - 1].split("\\.");
        var filename = tmp[tmp.length - 1].substring(0, tmp[tmp.length - 1].lastIndexOf("."));
        String extension = fileArr[fileArr.length-1];
        var mediaType = new MediaType("application", "force-download"); //octet-stream"

        if(StringUtil.isNotEmpty(extension)) {
            String[] strMediaType = UploadUtil.getMIMEType(extension).split("/");
            mediaType = new MediaType(strMediaType[0], strMediaType[1]);
        }

        return ResponseEntity.ok()
                .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + URLEncoder.encode(String.format("%s.%s", filename, extension), "UTF-8"))
                .contentType(mediaType)
                .body(bytes);
    }

    /***
     * 视频文件播放
     * @param field 路径存放字段
     * @param id 数据id
     * @param index 文件列表索引
     * @return
     */
    @PreAction
    @PlayVideoAction
    @ResponseBody
    @Operation(summary = "播放视频", method = "GET")
    @RequestMapping( value = "play/{field}/{id}/{index}", method = RequestMethod.GET )
    public Mono<Void> doPlayVideo(@Parameter(name="field", description = "字段名", required = true) @PathVariable String field,
                                  @Parameter(description = "ID", required = true) @PathVariable Long id,
                                  @Parameter(name = "index", description = "文件索引", required = true) @PathVariable Integer index,
                                  ServerWebExchange exchange
    ) throws Exception {

        String table = requestMatchSchemaFilter.getTableName(this.getClass());
        var model = dataExecuor.getModel(table);
        String dbname = requestMatchSchemaFilter.getDBName();
        model = dataExecuor.getModelById(dbname, table, model, id);
        IBll ibll = bllFactory.getBll(this);
        Optional<ResponseData> optional = ibll.exec(model, ActionType.PLAY_VIDEO);
        if(optional.isPresent()) {
            return null;
        }
        var data = dataExecuor.select(dbname, table, model, id);
        var value = ReflectionUtil.getFieldValue(data, field);
        if(value == null || StringUtil.isEmpty(value.toString())) {
            return null;
        }
        var fileList = StringUtil.splitString2List(value.toString(), ";");

        index--;
        if(fileList.size()<0 || index>=fileList.size()) {
            return Mono.empty();
        }
        var uploadPath = getUploadPath(fileList.get(index));

        File file = new File(uploadPath);
        if("/".equals(uploadPath.substring(0,1)) && !file.exists()) {
            uploadPath = String.format("%s%s", System.getProperty("user.dir"), uploadPath);
        }


        return doPlayVideo(exchange, uploadPath);
    }

    protected Mono<Void> doPlayVideo(ServerWebExchange exchange, String uploadPath) {
        FileSystemResource resource = new FileSystemResource(uploadPath);
        return getPlayVideoHandler().call(resource, exchange);
    }

    /***
     * 导出数据
     * @param queryData 查询参数
     * @throws Exception
     * @return
     */
    @PreAction
    @ExportAction
    @ResponseBody
    @Operation(summary = "导出数据", method = "POST")
    @RequestMapping( value = "export", method = RequestMethod.POST )
    public ResponseEntity<byte[]> doExportExcel(@Parameter(name="queryData", description = "查询参数", required = true) @RequestBody T queryData) throws Exception {
        try {
            String table = requestMatchSchemaFilter.getTableName(this.getClass());
            var model = dataExecuor.getModel(this.getClass(), queryData);
            IBll ibll = bllFactory.getBll(this);
            Optional<ResponseData> optional = ibll.exec(model, ActionType.EXPORT);
            if(optional.isPresent()) {
                if(optional.get()!=null && (optional.get().getData() instanceof List<?>) &&
                        ((List)optional.get().getData()).size()>0 && (((List)optional.get().getData()).get(0) instanceof BaseModel)) {
                    List<? extends BaseModel> data = (List<? extends BaseModel>) optional.get().getData();
                    return doExportExcel(data);
                }
                else {
                    return null;
                }
            }
            String dbname = requestMatchSchemaFilter.getDBName();
            var list = dataExecuor.select(dbname, table, model);
            if(list==null || list.size()<1) {
                throw new Exception("找不到需要导出的数据");
            }

            return doExportExcel(list);
        }
        catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return null;
    }

    @NotNull
    protected ResponseEntity<byte[]> doExportExcel(List<? extends BaseModel> list) throws UnsupportedEncodingException {
        if(list==null || list.size()<1) {
            throw new ParamException("没有需要导出的数据");
        }
        byte[] bytes = getDataExportHandler().call(list);
        var filename = UUID.randomUUID().toString().replace("-", "") + ExcelTypeEnum.XLSX.getValue();
        return ResponseEntity.ok()
                .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + URLEncoder.encode(filename, "UTF-8"))
                .contentType(new MediaType("application", "vnd.openxmlformats-officedocument.spreadsheetml.sheet"))
                .body(bytes);
    }

    /***
     * 导入Excel
     * @param file 上传导入文件
     * @return
     */
    @PreAction
    @ImportAction
    @ResponseBody
    @Operation(summary = "导入Excel", method = "POST")
    @RequestMapping( value = "import", method = {RequestMethod.POST} )
    public Mono<ResponseData<String>> doImportExcel(@Parameter(name="file", description = "上传导入文件", required = true) @RequestPart("file") Flux<FilePart> file) throws Exception {
        if(StringUtil.isEmpty(baseUploadPath)) {
            throw new Exception("Can not find abc.upload.path has been setting!!!");
        }
        String table = requestMatchSchemaFilter.getTableName(this.getClass());
        var model = dataExecuor.getModel(table);
        IBll ibll = bllFactory.getBll(this);
        Optional<ResponseData> optional = ibll.exec(model, ActionType.IMPORT);
        if(optional.isPresent()) {
            return null;
        }
        return file.flatMap(filePart-> {
            return filePart.content().map(dataBuffer -> {
                getDataImportHandler().call(requestMatchSchemaFilter.getDBName(), table, model, dataBuffer);
                return Mono.empty();
            });

        }).collectList().map(a-> success(String.format("total: %s", a.size()))).cache();
    }

    private String getUploadPath(String path) {
        String root = baseUploadPath.startsWith("/")? baseUploadPath: String.format("/%s", baseUploadPath);
        log.info("upload config path===>" + baseUploadPath);
        if (baseUploadPath.lastIndexOf("/") == baseUploadPath.length() - 1 ||
                baseUploadPath.lastIndexOf("\\") == baseUploadPath.length() - 1) {
            root = baseUploadPath.substring(0, baseUploadPath.length() - 2);
        }

        if ("/".equals(path.substring(0, 1))) {
            path = path.substring(1);
        }
        String filePath = String.format("%s/%s", root, path);
        log.info("download file path===>" + filePath);
        if (OSUtil.isWindows()) {
            filePath = System.getProperty("user.dir") + filePath;
            filePath = filePath.replaceAll("/", "\\\\");
        }

        return filePath;
    }

    public static boolean hasPrimitive(Object obj) {
        try {
            return ((Class<?>) obj.getClass().getField("TYPE").get(null)).isPrimitive();
        } catch (Exception e) {
            return false;
        }
    }

    protected String md5(String str) throws UnsupportedEncodingException, NoSuchAlgorithmException {
        if(StringUtil.isEmpty(md5PublicKey)) {
            md5PublicKey = LOG_PRE_SUFFIX;
        }
        return EncryptionUtil.md5(str, md5PublicKey, ENCODING);
    }
}
